using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using SdlDotNet.Windows;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Primitives;
using SdlDotNet.Graphics.Sprites;
using ElteIk.RoboSoccer.Core;
using ElteIk.RoboSoccer.Core.Physics;
using System.Collections;
using Tao.Sdl;

namespace ElteIk.RoboSoccer.SDL
{
    /// <summary>
    /// Graphics Engine that uses SDL to show a running game.
    /// </summary>
    public class SdlGraphicsEngine : ElteIk.RoboSoccer.Graphics.GraphicsEngine
    {
        private SurfaceControl renderSurfaceControl;
        private Surface renderSurface;
        private Surface fieldSurface;
        private System.Drawing.Point fieldUpperLeftPoint;
        private float ratio;
        private string graphicsPath = Path.Combine("..",
            Path.Combine("..", Path.Combine("..", Path.Combine("..", "Graphics"))));
        private string fontPath = Path.Combine("..",
            Path.Combine("..", Path.Combine("..", Path.Combine("..", "Fonts"))));
        private Surface formattingSurface = null;

        public SdlGraphicsEngine(SoccerGame game, SurfaceControl renderSurfaceControl)
            : base(game)
        {
            if ( renderSurfaceControl == null )
            {
				throw new ArgumentNullException("renderSurfaceControl", "You must provide a valid SdlDotNet.Windows.SurfaceControl instance to render on.");
            }
            this.renderSurfaceControl = renderSurfaceControl;
            formattingSurface = new Surface(renderSurfaceControl.Width, renderSurfaceControl.Height, 32, true);
            Initialize();
        }

        private static object renderLock = new object();

        public void Initialize()
        {
            //Video.SetVideoMode(1024, 768);
            //Video.Screen.Fill(Color.Black);
            lock (renderLock)
            {
                InitializeRenderSurface();
                InitializeSoccerField();
                InitializeBall();
                InitializeRobots();
                InitializeSurfaceBlitting();
                BlitTiled(renderSurface, backgroundSurface);
            }
        }

        private Surface backgroundSurface;
        protected void InitializeRenderSurface()
        {
            backgroundSurface = new Surface(Path.Combine(graphicsPath, "Back.png") );
            backgroundSurface = backgroundSurface.Convert(formattingSurface, true, true);
            renderSurface = new Surface(renderSurfaceControl.Width, renderSurfaceControl.Height);
            renderSurface = renderSurface.Convert(formattingSurface, true, true);
            renderSurface.Fill(Color.Black);
        }

        protected void BlitTiled( Surface destinationSurface, Surface sourceSurface )
        {
            int x = 0;
            while (x < destinationSurface.Width)
            {
                int y = 0;
                while (y < destinationSurface.Height)
                {
                    destinationSurface.Blit(sourceSurface, new Point(x, y));
                    y += sourceSurface.Height;
                }
                x += sourceSurface.Width;
            }
        }

        protected class LineDescriptor
        {
            protected PointF start;
	        public System.Drawing.PointF Start
	        {
		        get { return start; }
		        set { start = value; }
	        }
            protected PointF end;
	        public System.Drawing.PointF End
	        {
		        get { return end; }
		        set { end = value; }
	        }
            protected bool mirrored;
	        public bool Mirrored
	        {
		        get { return mirrored; }
		        set { mirrored = value; }
	        }
            public LineDescriptor(PointF start, PointF end, bool mirrored)
            {
                this.start = start;
                this.end = end;
                this.mirrored = mirrored;
            }
        }

        protected void InitializeSoccerField()
        {
            float totalFieldWidth = game.Field.Width + 2 * game.Field.WallThickness;
            float totalFieldHeight = game.Field.Height + 2 * game.Field.WallThickness;

            float widthRatio = (float)renderSurface.Width / totalFieldWidth;
            float heightRatio = (float)renderSurface.Height / totalFieldHeight;

            ratio = Math.Min(widthRatio, heightRatio);
            float widthDifference = renderSurface.Width - ratio * totalFieldWidth;

            fieldUpperLeftPoint = new Point((int)(widthDifference / 2), 0);
            
            fieldSurface = new Surface(Convert.ToInt32(totalFieldWidth * ratio), Convert.ToInt32(totalFieldHeight * ratio));
            fieldSurface = fieldSurface.Convert(formattingSurface, true, true);

            // Ground
            Surface groundSurface = new Surface(Path.Combine(graphicsPath, "Ground.jpg"));
            groundSurface = groundSurface.Convert(formattingSurface, true, true);
            BlitTiled(fieldSurface, groundSurface);

            // Lines on MIRA soccer ground
            List<LineDescriptor> lines = new List<LineDescriptor>();
            lines.Add(new LineDescriptor (
                new PointF(0.5f, 0.0f),
                new PointF(0.5f, 1.0f),
                false
                ));
            lines.Add(new LineDescriptor (
                new PointF(0.0f, 0.3f / 1.3f),
                new PointF(0.15f / 1.5f, 0.3f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor (
                new PointF(0.0f, 1.0f / 1.3f),
                new PointF(0.15f / 1.5f, 1.0f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor (
                new PointF(0.15f / 1.5f, 0.3f / 1.3f),
                new PointF(0.15f / 1.5f, 1.0f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.0f, 0.45f / 1.3f),
                new PointF(0.0f / 1.5f, 0.85f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.0f, 0.45f / 1.3f),
                new PointF(0.0f / 1.5f, 0.85f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.36f / 1.5f, 0.25f / 1.3f),
                new PointF(0.39f / 1.5f, 0.25f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.375f / 1.5f, 0.235f / 1.3f),
                new PointF(0.375f / 1.5f, 0.265f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.36f / 1.5f, 0.65f / 1.3f),
                new PointF(0.39f / 1.5f, 0.65f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.375f / 1.5f, 0.635f / 1.3f),
                new PointF(0.375f / 1.5f, 0.665f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.36f / 1.5f, 1.05f / 1.3f),
                new PointF(0.39f / 1.5f, 1.05f / 1.3f),
                true
                ));
            lines.Add(new LineDescriptor(
                new PointF(0.375f / 1.5f, 1.035f / 1.3f),
                new PointF(0.375f / 1.5f, 1.065f / 1.3f),
                true
                ));

            foreach ( LineDescriptor descriptor in lines )
            {
                Line line = new Line(
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + descriptor.Start.X * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + descriptor.Start.Y * game.Field.Height) * ratio)
                        ),
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + descriptor.End.X * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + descriptor.End.Y * game.Field.Height) * ratio)
                        )
                );
                fieldSurface.Draw( line, Color.White );
                if ( descriptor.Mirrored )
                {
                    line = new Line(
                        new Point(
                            Convert.ToInt32((game.Field.WallThickness + (1 - descriptor.Start.X) * game.Field.Width) * ratio),
                            Convert.ToInt32((game.Field.WallThickness + descriptor.Start.Y * game.Field.Height) * ratio)
                            ),
                        new Point(
                            Convert.ToInt32((game.Field.WallThickness + (1 - descriptor.End.X) * game.Field.Width) * ratio),
                            Convert.ToInt32((game.Field.WallThickness + descriptor.End.Y * game.Field.Height) * ratio)
                            )
                    );
                    fieldSurface.Draw( line, Color.White );
                }
            }

            fieldSurface.Draw(new SdlDotNet.Graphics.Primitives.Circle( new Point(
                    Convert.ToInt32((game.Field.WallThickness + 0.5 * game.Field.Width) * ratio),
                    Convert.ToInt32((game.Field.WallThickness + 0.5 * game.Field.Height) * ratio)
                    ), Convert.ToInt16(20 * ratio)), Color.White);

            Point[] bezierPoints = {
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 0.15f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.55f / 1.3f * game.Field.Height) * ratio)
                        ),
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 0.2f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.65f / 1.3f * game.Field.Height) * ratio)
                        ),
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 0.15f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.75f / 1.3f * game.Field.Height) * ratio)
                        )
                    };
            fieldSurface.Draw(new SdlDotNet.Graphics.Primitives.Triangle(
                bezierPoints[0], bezierPoints[1], bezierPoints[2]), Color.White);

            Point[] bezierPoints2 = {
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 1.35f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.55f / 1.3f * game.Field.Height) * ratio)
                        ),
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 1.3f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.65f / 1.3f * game.Field.Height) * ratio)
                        ),
                    new Point(
                        Convert.ToInt32((game.Field.WallThickness + 1.35f / 1.5f * game.Field.Width) * ratio),
                        Convert.ToInt32((game.Field.WallThickness + 0.75f / 1.3f * game.Field.Height) * ratio)
                        )
                    };
            fieldSurface.Draw(new SdlDotNet.Graphics.Primitives.Triangle(
                bezierPoints2[0], bezierPoints2[1], bezierPoints2[2]), Color.White);

            // Corners & walls
            Surface wallSurface = new Surface(Path.Combine(graphicsPath, "Wall.jpg"));
            wallSurface = wallSurface.Convert(formattingSurface, true, true);
            foreach (Corner corner in game.Field.Corners)
            {
                // Converting the corner points to polygon points where the SDL polygon can be drawn
                ArrayList cornerPoints = new ArrayList(
                    corner.Points.ConvertAll<Point>(
                        delegate(Vector2D source )
                        {
                            return new Point(
                                Convert.ToInt32( (corner.Center.X + source.X) * ratio ),
                                Convert.ToInt32( (corner.Center.Y + source.Y) * ratio ) );
                        }
                    ).ToArray()
                );
                fieldSurface.Draw(
                    new TexturedPolygon(wallSurface, cornerPoints, new Point(0, 0)), Color.White, true, true
                    );
            }
            foreach (Wall wall in game.Field.Walls)
            {
                // Converting the wall points to polygon points where the SDL polygon can be drawn
                ArrayList cornerPoints = new ArrayList(
                    wall.Points.ConvertAll<Point>(
                        delegate(Vector2D source)
                        {
                            return new Point(
                                Convert.ToInt32((wall.Position.X + source.X) * ratio),
                                Convert.ToInt32((wall.Position.Y + source.Y) * ratio));
                        }
                    ).ToArray()
                );
                fieldSurface.Draw(
                    new TexturedPolygon(wallSurface, cornerPoints, new Point(0, 0)), Color.White, true, true
                    );
            }
            SdlDotNet.Graphics.Font showg =
                new SdlDotNet.Graphics.Font(Path.Combine(fontPath, "Showg.ttf"), Convert.ToInt32(15 * ratio));
            TextSprite firaSprite = new TextSprite("FIRA", showg, Color.FromArgb(200, 160, 160, 200));
            firaSprite.AlphaBlending = true;
            fieldSurface.Blit(firaSprite, new Point(
                Convert.ToInt32((game.Field.WallThickness + 0.5 * game.Field.Width) * ratio - 0.5 * firaSprite.Width),
                Convert.ToInt32((game.Field.WallThickness + 0.5 * game.Field.Height) * ratio - 0.5 * firaSprite.Height)
                ) );
        }

        protected SpriteCollection robots = new SpriteCollection();
        protected Surface leftTeamSurface = null;
        protected Surface rightTeamSurface = null;
        protected string leftTeamImage = null;
        protected string rightTeamImage = null;
        protected float robotSize = 0;
        protected void InitializeRobots()
        {
            ElteIk.RoboSoccer.Core.Physics.Polygon playerPolygon =
                game.LeftTeam.Players[0] as ElteIk.RoboSoccer.Core.Physics.Polygon;
            robotSize = (playerPolygon.Points[1] - playerPolygon.Points[0]).Length;
            if ( leftTeamImage == null )
                leftTeamSurface = new Surface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
            else
            {
                leftTeamSurface = new Surface(leftTeamImage);
                leftTeamSurface = leftTeamSurface.CreateStretchedSurface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
            }
            if ( rightTeamImage == null )
                rightTeamSurface = new Surface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
            else
            {
                rightTeamSurface = new Surface(rightTeamImage);
                rightTeamSurface = rightTeamSurface.CreateStretchedSurface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
            }
        }

        Sprite ballSprite = null;
        protected void InitializeBall()
        {
            ballSprite = new Sprite(Path.Combine(graphicsPath, "Ball.png"));
            ballSprite.Surface = ballSprite.Surface.CreateStretchedSurface(new Size(Convert.ToInt32(game.Ball.Radius * ratio * 2), Convert.ToInt32(game.Ball.Radius * ratio * 2)));
        }

        #region Fast surface control blitting
        
        // It would be neater to handle these things in a class derived from Surface

        protected MemoryStream bitmapStream;
        protected IntPtr bitmapPtr;
        protected byte[] bitmapBuffer;
        protected List<System.Drawing.Rectangle> invalidatedRegions = new List<System.Drawing.Rectangle>();

        private void InitializeSurfaceBlitting()
        {
            // FIXME: magic number!!! This is because renderSurface.BmpHeader is private
            int length = (renderSurface.Width * renderSurface.Height * renderSurface.BitsPerPixel) + 54;
            bitmapBuffer = new byte[length];
            bitmapPtr = Marshal.AllocHGlobal(bitmapBuffer.Length);
            bitmapStream = new MemoryStream(bitmapBuffer, 0, bitmapBuffer.Length);
            Sdl.SDL_SaveBMP_RW(renderSurface.Handle, Sdl.SDL_RWFromMem(bitmapPtr, bitmapBuffer.Length), 1);
            Marshal.Copy(bitmapPtr, bitmapBuffer, 0, bitmapBuffer.Length);
            renderSurfaceControl.Image = (Bitmap)Bitmap.FromStream(bitmapStream);
        }

        private void DestroySurfaceBlitting()
        {
            bitmapStream.Dispose();
            Marshal.FreeHGlobal(bitmapPtr);
        }

        private void BlitSurface()
        {
            // I know, this is quite ugly...
            System.Drawing.Imaging.PixelFormat pxf = System.Drawing.Imaging.PixelFormat.Format24bppRgb;

            Bitmap bmp = renderSurfaceControl.Image as Bitmap;
            System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, renderSurface.Width, renderSurface.Height);
            System.Drawing.Imaging.BitmapData bitmapData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, pxf);
            IntPtr realBitmapPtr = bitmapData.Scan0;

            Sdl.SDL_SaveBMP_RW(renderSurface.Handle, Sdl.SDL_RWFromMem(bitmapPtr, bitmapBuffer.Length), 0);
            Marshal.Copy(bitmapPtr, bitmapBuffer, 0, bitmapBuffer.Length);
            Marshal.Copy(bitmapBuffer, 54, realBitmapPtr, renderSurface.Width * renderSurface.Height * 3);

            bmp.UnlockBits(bitmapData);

            // Result is flipped because SDL saves it like that...
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);

            // 1st alternative:

            //Video.Screen.Blit(renderSurface);
            //Video.Update();

            // 2nd (slower) alternative:
            //renderSurfaceControl.Image = (Bitmap)Bitmap.FromStream(bitmapStream);
        }

        #endregion

        public override void Update()
	    {
            lock (renderLock)
            {
                if (renderSurface.Width != renderSurfaceControl.Width || renderSurface.Height != renderSurfaceControl.Height)
                    Initialize();

                //BlitTiled(renderSurface, backgroundSurface);
                DrawField(game.Field);
                DrawBall();
                DrawPlayers();
                BlitSurface();
            }
	    }

        public void UpdateControl()
        {
            lock (renderLock)
            {
                //foreach (System.Drawing.Rectangle region in invalidatedRegions)
                //{
                //    this.renderSurfaceControl.Invalidate(region);
                //}
                //invalidatedRegions.Clear();
                this.renderSurfaceControl.Invalidate();
                //BlitSurface();
            }
        }

        protected void DrawPlayers()
        {
            foreach (IPlayer player in game.LeftTeam.Players)
            {
                Surface playerSurface = leftTeamSurface.CreateRotatedSurface(Convert.ToInt32(-player.Rotation));
                int x = Convert.ToInt32(fieldUpperLeftPoint.X + (player.Position.X) * ratio - 0.5f * playerSurface.Width);
                int y = Convert.ToInt32(fieldUpperLeftPoint.Y + (player.Position.Y) * ratio - 0.5f * playerSurface.Height);
                renderSurface.Blit(playerSurface, new Point(x, y));
                //invalidatedRegions.Add( new System.Drawing.Rectangle( x, y, System.Convert.ToInt32(playerSurface.Width * ratio), System.Convert.ToInt32(playerSurface.Height * ratio) ));
            }
            foreach (IPlayer player in game.RightTeam.Players)
            {
                Surface playerSurface = rightTeamSurface.CreateRotatedSurface(Convert.ToInt32(-player.Rotation));
                int x = Convert.ToInt32(fieldUpperLeftPoint.X + (player.Position.X) * ratio - 0.5f * playerSurface.Width);
                int y = Convert.ToInt32(fieldUpperLeftPoint.Y + (player.Position.Y) * ratio - 0.5f * playerSurface.Height);
                renderSurface.Blit(playerSurface, new Point(x, y));
                //invalidatedRegions.Add(new System.Drawing.Rectangle(x, y, System.Convert.ToInt32(playerSurface.Width * ratio), System.Convert.ToInt32(playerSurface.Height * ratio)));
            }
        }

        protected override void DrawField(SoccerField field)
        {
            renderSurface.Blit(fieldSurface, fieldUpperLeftPoint);
        }

        protected void DrawBall()
        {
            renderSurface.Blit(ballSprite, new Point(
                Convert.ToInt32(fieldUpperLeftPoint.X + (game.Ball.Position.X - game.Ball.Radius) * ratio),
                Convert.ToInt32(fieldUpperLeftPoint.Y + (game.Ball.Position.Y - game.Ball.Radius) * ratio)));
        }

        public override void ChangeTeamPatch(int teamId, string imageFilename)
        {
            if (teamId == 1)
            {
                leftTeamImage = imageFilename;
                leftTeamSurface = new Surface(imageFilename);
                leftTeamSurface = leftTeamSurface.CreateStretchedSurface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
                Update();
            }
            else if (teamId == 2)
            {
                rightTeamImage = imageFilename;
                rightTeamSurface = new Surface(imageFilename);
                rightTeamSurface = rightTeamSurface.CreateStretchedSurface(new Size(Convert.ToInt32(robotSize * ratio), Convert.ToInt32(robotSize * ratio)));
                Update();
            }
        }

        public override void Resize()
        {
            DestroySurfaceBlitting();
            InitializeSurfaceBlitting();
            Update();
        }

        ~SdlGraphicsEngine()
        {
            DestroySurfaceBlitting();
        }
    }

}
